#include <SDL.h>
#include "wxvbam.h"
#include "../common/ConfigManager.h"
#include "../common/SoundSDL.h"
#include <wx/ffile.h>
#include <wx/print.h>
#include <wx/printdlg.h>
#include <wx/generic/prntdlgg.h>

// These should probably be in vbamcore
int systemVerbose;
int systemFrameSkip;

int systemRedShift;
int systemGreenShift;
int systemBlueShift;
int systemColorDepth;
u16 systemColorMap16[0x10000];
u32 systemColorMap32[0x10000];
#define gs555(x) (x | (x << 5) | (x << 10))
u16 systemGbPalette[24] =
{
	gs555(0x1f), gs555(0x15), gs555(0x0c), 0,
	gs555(0x1f), gs555(0x15), gs555(0x0c), 0,
	gs555(0x1f), gs555(0x15), gs555(0x0c), 0,
	gs555(0x1f), gs555(0x15), gs555(0x0c), 0,
	gs555(0x1f), gs555(0x15), gs555(0x0c), 0,
	gs555(0x1f), gs555(0x15), gs555(0x0c), 0
};
int RGB_LOW_BITS_MASK;

// these are local, though.
int joypress[4], autofire;
static int sensorx[4], sensory[4], sensorz[4];
bool pause_next;
bool turbo;

// and this is from MFC interface
bool soundBufferLow;

void systemMessage(int id, const char* fmt, ...)
{
	static char* buf = NULL;
	static int buflen = 80;
	va_list args;
	// auto-conversion of wxCharBuffer to const char * seems broken
	// so save underlying wxCharBuffer (or create one of none is used)
	wxCharBuffer _fmt(wxString(wxGetTranslation(wxString(fmt, wxConvLibc))).utf8_str());

	if (!buf)
	{
		buf = (char*)malloc(buflen);

		if (!buf)
			exit(1);
	}

	while (1)
	{
		va_start(args, fmt);
		int needsz = vsnprintf(buf, buflen, _fmt.data(), args);
		va_end(args);

		if (needsz < buflen)
			break;

		while (buflen <= needsz)
			buflen *= 2;

		free(buf);
		buf = (char*)malloc(buflen);

		if (!buf)
			exit(1);
	}

	wxLogError(wxT("%s"), wxString(buf, wxConvLibc).c_str());
}

static int frames = 0;

void systemDrawScreen()
{
	frames++;
	MainFrame* mf = wxGetApp().frame;
	mf->UpdateViewers();
	// FIXME: Sm60FPS crap and sondBufferLow crap
	GameArea* ga = mf->GetPanel();
#ifndef NO_FFMPEG

	if (ga)
		ga->AddFrame(pix);

#endif

	if (ga && ga->panel)
		ga->panel->DrawArea(&pix);
}

// record a game "movie"
// actually just game save state combined with a keystroke log
// doesn't work in GB "multiplayer" mode (only records default joypad)
//
//  <name>.vmv = keystroke log; all values little-endian ints:
//     <version>.32 = 1
//     for every joypad change (init to 0) and once at end of movie {
//        <timestamp>.32 = frames since start of movie
//        <joypad>.32 = default joypad reading at that time
//     }
//  <name>.vm0 = saved state

wxFFile game_file;
bool game_recording, game_playback;
u32 game_frame;
u32 game_joypad;

void systemStartGameRecording(const wxString &fname)
{
	GameArea* panel = wxGetApp().frame->GetPanel();

	if (!panel || panel->game_type() == IMAGE_UNKNOWN ||
	        !panel->emusys->emuWriteState)
	{
		wxLogError(_("No game in progress to record"));
		return;
	}

	systemStopGamePlayback();
	wxString fn = fname;

	if (fn.size() < 4 || !wxString(fn.substr(fn.size() - 4)).IsSameAs(wxT(".vmv"), false))
		fn.append(wxT(".vmv"));

	u32 version = 1;

	if (!game_file.Open(fn.c_str(), wxT("wb")) ||
	        game_file.Write(&version, sizeof(version)) != sizeof(version))
	{
		wxLogError(_("Cannot open output file %s"), fname.c_str());
		return;
	}

	fn[fn.size() - 1] = wxT('0');

	if (!panel->emusys->emuWriteState(fn.mb_fn_str()))
	{
		wxLogError(_("Error writing game recording"));
		game_file.Close();
		return;
	}

	game_frame = 0;
	game_joypad = 0;
	game_recording = true;
	MainFrame* mf = wxGetApp().frame;
	mf->cmd_enable &= ~(CMDEN_NGREC | CMDEN_GPLAY | CMDEN_NGPLAY);
	mf->cmd_enable |= CMDEN_GREC;
	mf->enable_menus();
}

void systemStopGameRecording()
{
	if (!game_recording)
		return;

	if (game_file.Write(&game_frame, sizeof(game_frame)) != sizeof(game_frame) ||
	        game_file.Write(&game_joypad, sizeof(game_joypad)) != sizeof(game_joypad) ||
	        !game_file.Close())
		wxLogError(_("Error writing game recording"));

	game_recording = false;
	MainFrame* mf = wxGetApp().frame;
	mf->cmd_enable &= ~CMDEN_GREC;
	mf->cmd_enable |= CMDEN_NGREC | CMDEN_NGPLAY;
	mf->enable_menus();
}

u32 game_next_frame, game_next_joypad;

void systemStartGamePlayback(const wxString &fname)
{
	GameArea* panel = wxGetApp().frame->GetPanel();

	if (!panel || panel->game_type() == IMAGE_UNKNOWN ||
	        !panel->emusys->emuReadState)
	{
		wxLogError(_("No game in progress to record"));
		return;
	}

	if (game_recording)
	{
		wxLogError(_("Cannot play game recording while recording"));
		return;
	}

	systemStopGamePlayback();
	wxString fn = fname;

	if (fn.size() < 4 || !wxString(fn.substr(fn.size() - 4)).IsSameAs(wxT(".vmv"), false))
		fn.append(wxT(".vmv"));

	u32 version;

	if (!game_file.Open(fn.c_str(), wxT("rb")) ||
	        game_file.Read(&version, sizeof(version)) != sizeof(version) ||
	        wxUINT32_SWAP_ON_BE(version) != 1)
	{
		wxLogError(_("Cannot open recording file %s"), fname.c_str());
		return;
	}

	u32 gf, jp;

	if (game_file.Read(&gf, sizeof(gf)) != sizeof(gf) ||
	        game_file.Read(&jp, sizeof(jp)) != sizeof(jp))
	{
		wxLogError(_("Error reading game recording"));
		game_file.Close();
		return;
	}

	game_next_frame = wxUINT32_SWAP_ON_BE(gf);
	game_next_joypad = wxUINT32_SWAP_ON_BE(jp);
	fn[fn.size() - 1] = wxT('0');

	if (!panel->emusys->emuReadState(fn.mb_fn_str()))
	{
		wxLogError(_("Error reading game recording"));
		game_file.Close();
		return;
	}

	game_frame = 0;
	game_joypad = 0;
	game_playback = true;
	MainFrame* mf = wxGetApp().frame;
	mf->cmd_enable &= ~(CMDEN_NGREC | CMDEN_GREC | CMDEN_NGPLAY);
	mf->cmd_enable |= CMDEN_GPLAY;
	mf->enable_menus();
}

void systemStopGamePlayback()
{
	if (!game_playback)
		return;

	game_file.Close();
	game_playback = false;
	MainFrame* mf = wxGetApp().frame;
	mf->cmd_enable &= ~CMDEN_GPLAY;
	mf->cmd_enable |= CMDEN_NGREC | CMDEN_NGPLAY;
	mf->enable_menus();
}



// updates the joystick data (done in background using wxSDLJoy)
bool systemReadJoypads()
{
	return true;
}

// return information about the given joystick, -1 for default joystick
u32 systemReadJoypad(int joy)
{
	if (joy < 0 || joy > 3)
		joy = gopts.default_stick - 1;

	uint32_t ret = joypress[joy];

	if (turbo)
		ret |= KEYM_SPEED;

	u32 af = autofire;

	if (ret & KEYM_AUTO_A)
	{
		ret |= KEYM_A;
		af |= KEYM_A;
	}

	if (ret & KEYM_AUTO_B)
	{
		ret |= KEYM_B;
		af |= KEYM_B;
	}

	static int autofire_trigger = 1;
	static bool autofire_state = true;
	u32 af_but = af & ret;

	if (af_but)
	{
		if (!autofire_state)
			ret &= ~af_but;

		if (!--autofire_trigger)
		{
			autofire_trigger = gopts.autofire_rate;
			autofire_state = !autofire_state;
		}
	}
	else
	{
		autofire_state = true;
		autofire_trigger = gopts.autofire_rate;
	}

	// disallow opposite directionals simultaneously
	ret &= ~((ret & (KEYM_LEFT | KEYM_DOWN | KEYM_MOTION_DOWN | KEYM_MOTION_RIGHT)) >> 1);
	ret &= REALKEY_MASK;

	if (game_recording)
	{
		u32 rret = ret & ~(KEYM_SPEED | KEYM_CAPTURE);

		if (rret != game_joypad)
		{
			game_joypad = rret;
			u32 gf = wxUINT32_SWAP_ON_BE(game_frame);
			u32 jp = wxUINT32_SWAP_ON_BE(game_joypad);

			if (game_file.Write(&gf, sizeof(gf)) != sizeof(gf) ||
			        game_file.Write(&jp, sizeof(jp)) != sizeof(jp))
			{
				game_file.Close();
				game_recording = false;
				wxLogError(_("Error writing game recording"));
			}
		}
	}
	else if (game_playback)
	{
		while (game_frame >= game_next_frame)
		{
			game_joypad = game_next_joypad;
			u32 gf, jp;

			if (game_file.Read(&gf, sizeof(gf)) != sizeof(gf) ||
			        game_file.Read(&jp, sizeof(jp)) != sizeof(jp))
			{
				game_file.Close();
				game_playback = false;
				wxString msg(_("Playback ended"));
				systemScreenMessage(msg);
				break;
			}

			game_next_frame = wxUINT32_SWAP_ON_BE(gf);
			game_next_joypad = wxUINT32_SWAP_ON_BE(jp);
		}

		ret = game_joypad;
	}

	return ret;
}

void systemShowSpeed(int speed)
{
	MainFrame* f = wxGetApp().frame;
	wxString s;
	s.Printf(_("%d%%(%d, %d fps)"), speed, systemFrameSkip, frames * speed / 100);

	switch (showSpeed)
	{
	case SS_NONE:
		f->GetPanel()->osdstat.clear();
		break;

	case SS_PERCENT:
		f->GetPanel()->osdstat.Printf(_("%d%%"), speed);
		break;

	case SS_DETAILED:
		f->GetPanel()->osdstat = s;
		break;
	}

	wxGetApp().frame->SetStatusText(s, 1);
	frames = 0;
}

int systemSaveUpdateCounter = SYSTEM_SAVE_NOT_UPDATED;

void system10Frames(int rate)
{
	GameArea* panel = wxGetApp().frame->GetPanel();
	int fs = frameSkip;

	if (fs < 0)
	{
		// I don't know why this algorithm isn't in common somewhere
		// as is, I copied it from SDL
		static u32 prevclock = 0;
		static int speedadj = 0;
		u32 t = systemGetClock();

		if (!panel->was_paused && prevclock && (t - prevclock) != 10000 / rate)
		{
			int speed = t == prevclock ? 100 * 10000 / rate - (t - prevclock) : 100;

			// why 98??
			if (speed >= 98)
				speedadj++;
			else if (speed < 80)
				speedadj -= (90 - speed) / 5;
			else
				speedadj--;

			if (speedadj >= 3)
			{
				speedadj = 0;

				if (systemFrameSkip > 0)
					systemFrameSkip--;
			}
			else if (speedadj <= -2)
			{
				speedadj += 2;

				if (systemFrameSkip < 9)
					systemFrameSkip++;
			}
		}

		prevclock = t;
		panel->was_paused = false;
	}

	if (gopts.rewind_interval)
	{
		if (!panel->rewind_time)
			panel->rewind_time = gopts.rewind_interval * 6;
		else if (!--panel->rewind_time)
			panel->do_rewind = true;
	}

	if (--systemSaveUpdateCounter == SYSTEM_SAVE_NOT_UPDATED)
		panel->SaveBattery();
	else if (systemSaveUpdateCounter < SYSTEM_SAVE_NOT_UPDATED)
		systemSaveUpdateCounter = SYSTEM_SAVE_NOT_UPDATED;
}

void systemFrame()
{
	if (game_recording || game_playback)
		game_frame++;
}

// technically, num is ignored in favor of finding the first
// available slot
void systemScreenCapture(int num)
{
	GameArea* panel = wxGetApp().frame->GetPanel();
	wxFileName fn = wxFileName(wxGetApp().frame->GetGamePath(gopts.scrshot_dir), wxEmptyString);

	do
	{
		wxString bfn;
		bfn.Printf(wxT("%s%02d"), panel->game_name().c_str(),
		           num++);

		if (captureFormat == 0)
			bfn.append(wxT(".png"));
		else // if(gopts.cap_format == 1)
			bfn.append(wxT(".bmp"));

		fn.SetFullName(bfn);
	}
	while (fn.FileExists());

	fn.Mkdir(0777, wxPATH_MKDIR_FULL);

	if (captureFormat == 0)
		panel->emusys->emuWritePNG(fn.GetFullPath().mb_fn_str());
	else // if(gopts.cap_format == 1)
		panel->emusys->emuWriteBMP(fn.GetFullPath().mb_fn_str());

	wxString msg;
	msg.Printf(_("Wrote snapshot %s"), fn.GetFullPath().c_str());
	systemScreenMessage(msg);
}

void systemSaveOldest()
{
	// I need to be implemented
}

void systemLoadRecent()
{
	// I need to be implemented
}


u32 systemGetClock()
{
	return wxGetApp().timer.Time();
}

void systemCartridgeRumble(bool) {}

static u8 sensorDarkness = 0xE8; // total darkness (including daylight on rainy days)

u8 systemGetSensorDarkness()
{
	return sensorDarkness;
}

void systemUpdateSolarSensor()
{
	u8 sun = 0x0; //sun = 0xE8 - 0xE8 (case 0 and default)
	int level = sunBars / 10;

	switch (level)
	{
	case 1:
		sun = 0xE8 - 0xE0;
		break;

	case 2:
		sun = 0xE8 - 0xDA;
		break;

	case 3:
		sun = 0xE8 - 0xD0;
		break;

	case 4:
		sun = 0xE8 - 0xC8;
		break;

	case 5:
		sun = 0xE8 - 0xC0;
		break;

	case 6:
		sun = 0xE8 - 0xB0;
		break;

	case 7:
		sun = 0xE8 - 0xA0;
		break;

	case 8:
		sun = 0xE8 - 0x88;
		break;

	case 9:
		sun = 0xE8 - 0x70;
		break;

	case 10:
		sun = 0xE8 - 0x50;
		break;

	default:
		break;
	}

	sensorDarkness = 0xE8 - sun;
}

void systemUpdateMotionSensor()
{
	for (int i = 0; i < 4; i++)
	{
		if (!sensorx[i])
			sensorx[i] = 2047;

		if (!sensory[i])
			sensory[i] = 2047;

		if (joypress[i] & KEYM_MOTION_LEFT)
		{
			sunBars--;

			if (sunBars < 1)
				sunBars = 1;

			sensorx[i] += 3;

			if (sensorx[i] > 2197)
				sensorx[i] = 2197;

			if (sensorx[i] < 2047)
				sensorx[i] = 2057;
		}
		else if (joypress[i] & KEYM_MOTION_RIGHT)
		{
			sunBars++;

			if (sunBars > 100)
				sunBars = 100;

			sensorx[i] -= 3;

			if (sensorx[i] < 1897)
				sensorx[i] = 1897;

			if (sensorx[i] > 2047)
				sensorx[i] = 2037;
		}
		else if (sensorx[i] > 2047)
		{
			sensorx[i] -= 2;

			if (sensorx[i] < 2047)
				sensorx[i] = 2047;
		}
		else
		{
			sensorx[i] += 2;

			if (sensorx[i] > 2047)
				sensorx[i] = 2047;
		}

		if (joypress[i] & KEYM_MOTION_UP)
		{
			sensory[i] += 3;

			if (sensory[i] > 2197)
				sensory[i] = 2197;

			if (sensory[i] < 2047)
				sensory[i] = 2057;
		}
		else if (joypress[i] & KEYM_MOTION_DOWN)
		{
			sensory[i] -= 3;

			if (sensory[i] < 1897)
				sensory[i] = 1897;

			if (sensory[i] > 2047)
				sensory[i] = 2037;
		}
		else if (sensory[i] > 2047)
		{
			sensory[i] -= 2;

			if (sensory[i] < 2047)
				sensory[i] = 2047;
		}
		else
		{
			sensory[i] += 2;

			if (sensory[i] > 2047)
				sensory[i] = 2047;
		}

		const int lowZ = -1800;
		const int centerZ = 0;
		const int highZ = 1800;
		const int accelZ = 3;

		if (joypress[i] & KEYM_MOTION_IN)
		{
			sensorz[i] += accelZ;

			if (sensorz[i] > highZ)
				sensorz[i] = highZ;

			if (sensorz[i] < centerZ)
				sensorz[i] = centerZ + (accelZ * 300);
		}
		else if (joypress[i] & KEYM_MOTION_OUT)
		{
			sensorz[i] -= accelZ;

			if (sensorz[i] < lowZ)
				sensorz[i] = lowZ;

			if (sensorz[i] > centerZ)
				sensorz[i] = centerZ - (accelZ * 300);
		}
		else if (sensorz[i] > centerZ)
		{
			sensorz[i] -= (accelZ * 100);

			if (sensorz[i] < centerZ)
				sensorz[i] = centerZ;
		}
		else
		{
			sensorz[i] += (accelZ * 100);

			if (sensorz[i] > centerZ)
				sensorz[i] = centerZ;
		}
	}

	systemUpdateSolarSensor();
}

int  systemGetSensorX()
{
	return sensorx[gopts.default_stick - 1];
}

int  systemGetSensorY()
{
	return sensory[gopts.default_stick - 1];
}

int  systemGetSensorZ()
{
	return sensorz[gopts.default_stick - 1] / 10;
}

class PrintDialog : public wxEvtHandler, public wxPrintout
{
public:
	PrintDialog(const u16* data, int lines, bool cont);
	~PrintDialog();
	int ShowModal()
	{
		dlg->SetWindowStyle(wxCAPTION | wxRESIZE_BORDER);

		if (gopts.keep_on_top)
			dlg->SetWindowStyle(dlg->GetWindowStyle() | wxSTAY_ON_TOP);
		else
			dlg->SetWindowStyle(dlg->GetWindowStyle() & ~wxSTAY_ON_TOP);

		CheckPointer(wxGetApp().frame);
		return wxGetApp().frame->ShowModal(dlg);
	}
private:
	void DoSave(wxCommandEvent &);
	void DoPrint(wxCommandEvent &);
	void ChangeMag(wxCommandEvent &);
	void ShowImg(wxPaintEvent &);
	bool OnPrintPage(int pno);
	void OnPreparePrinting();
	bool HasPage(int pno) { return pno <= npw * nph; }
	void GetPageInfo(int* minp, int* maxp, int* pfrom, int* pto)
	{
		*minp = 1; *maxp = npw * nph; *pfrom = 1; *pto = 1;
	}

	wxDialog* dlg;
	wxPanel* p;
	wxImage img;
	wxBitmap* bmp;
	wxControlWithItems* mag;

	static wxPrintData* printdata;
	static wxPageSetupDialogData* pagedata;
	wxRect margins;
	int npw, nph;

	DECLARE_CLASS()
};

IMPLEMENT_CLASS(PrintDialog, wxEvtHandler)

PrintDialog::PrintDialog(const u16* data, int lines, bool cont) :
	img(160, lines), npw(1), nph(1),
	wxPrintout(wxGetApp().frame->GetPanel()->game_name() + wxT(" Printout"))
{
	dlg = wxStaticCast(wxGetApp().frame->FindWindow(XRCID("GBPrinter")), wxDialog);
	p = XRCCTRL(*dlg, "Preview", wxPanel);
	wxScrolledWindow* pp = wxStaticCast(p->GetParent(), wxScrolledWindow);
	wxSize sz(320, lines * 2);
	p->SetSize(sz);
	pp->SetVirtualSize(sz);

	if (lines > 144)
		sz.SetHeight(144 * 2);

	pp->SetSizeHints(sz, sz); // keep sizer from messing with size
	pp->SetSize(sz);
	pp->SetScrollRate(1, 1);
	// why is this so difficult?
	// I want the display area to be 320x288, not the area w/ scrollbars
	wxSize csz = pp->GetClientSize();
	sz += sz - csz;
	pp->SetSizeHints(sz, sz); // keep sizer from messing with size
	pp->SetSize(sz);
	dlg->Fit();
	// what a waste.  wxImage is brain-dead rgb24
	data += 162; // top border

	for (int y = 0; y < lines; y++)
	{
		for (int x = 0; x < 160; x++)
		{
			u16 d = *data++;
			img.SetRGB(x, y, ((d >> 10) & 0x1f) << 3, ((d >> 5) & 0x1f) << 3,
			           (d & 0x1f) << 3);
		}

		data += 2; // rhs border
	}

	bmp = new wxBitmap(img.Scale(320, 2 * lines, wxIMAGE_QUALITY_HIGH));
	p->Connect(wxEVT_PAINT, wxPaintEventHandler(PrintDialog::ShowImg),
	           NULL, this);
	dlg->Connect(wxID_SAVE, wxEVT_COMMAND_BUTTON_CLICKED,
	             wxCommandEventHandler(PrintDialog::DoSave), NULL, this);
	dlg->Connect(wxID_PRINT, wxEVT_COMMAND_BUTTON_CLICKED,
	             wxCommandEventHandler(PrintDialog::DoPrint), NULL, this);
	dlg->Connect(XRCID("Magnification"), wxEVT_COMMAND_CHOICE_SELECTED,
	             wxCommandEventHandler(PrintDialog::ChangeMag), NULL, this);
	mag = XRCCTRL(*dlg, "Magnification", wxControlWithItems);
	mag->SetSelection(1);
	wxWindow* w = dlg->FindWindow(cont ? wxID_OK : wxID_SAVE);

	if (w)
		w->SetFocus();

	wxButton* cb = wxStaticCast(dlg->FindWindow(wxID_CANCEL), wxButton);

	if (cb)
		cb->SetLabel(_("&Discard"));
}

PrintDialog::~PrintDialog()
{
	p->Disconnect(wxID_ANY, wxEVT_PAINT);
	dlg->Disconnect(wxID_PRINT);
	dlg->Disconnect(wxID_SAVE);
	dlg->Disconnect(XRCID("Magnification"));
	delete bmp;
}

void PrintDialog::ShowImg(wxPaintEvent &evt)
{
	wxPaintDC dc(wxStaticCast(evt.GetEventObject(), wxWindow));
	dc.DrawBitmap(*bmp, 0, 0);
}


void PrintDialog::ChangeMag(wxCommandEvent &evt)
{
	int m = mag->GetSelection() + 1;
	wxScrolledWindow* pp = wxStaticCast(p->GetParent(), wxScrolledWindow);
	wxSize sz(m * 160, m * img.GetHeight());
	delete bmp;
	bmp = new wxBitmap(img.Scale(sz.GetWidth(), sz.GetHeight(), wxIMAGE_QUALITY_HIGH));
	p->SetSize(sz);
	pp->SetVirtualSize(sz);
//    pp->Refresh();
}

static wxString prsav_path;
void PrintDialog::DoSave(wxCommandEvent &)
{
	wxString pats = _("Image files (*.bmp;*.jpg;*.png)|*.bmp;*.jpg;*.png|");
	pats.append(wxALL_FILES);
	wxString dn = wxGetApp().frame->GetPanel()->game_name();

	if (captureFormat == 0)
		dn.append(wxT(".png"));
	else // if(gopts.cap_format == 1)
		dn.append(wxT(".bmp"));

	wxFileDialog fdlg(dlg, _("Save printer image to"), prsav_path, dn,
	                  pats, wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
	int ret = fdlg.ShowModal();
	prsav_path = fdlg.GetDirectory();

	if (ret != wxID_OK)
		return;

	wxString of = fdlg.GetPath();
	int m = mag->GetSelection() + 1;
	wxImage scimg = img.Scale(m * 160, m * img.GetHeight(), wxIMAGE_QUALITY_HIGH);

	if (scimg.SaveFile(of))
	{
		wxString msg;
		msg.Printf(_("Wrote printer output to %s"), of.c_str());
		systemScreenMessage(msg);
		wxButton* cb = wxStaticCast(dlg->FindWindow(wxID_CANCEL), wxButton);

		if (cb)
		{
			cb->SetLabel(_("&Close"));
			cb->SetFocus();
		}
	}
}

wxPrintData* PrintDialog::printdata = NULL;
wxPageSetupDialogData* PrintDialog::pagedata = NULL;

void PrintDialog::OnPreparePrinting()
{
	margins = GetLogicalPageMarginsRect(*pagedata);
	// strange... y is always negative
	margins.y = -margins.y;
	int w = bmp->GetWidth();
	int h = bmp->GetHeight();
	npw = (w + margins.width - 1) / margins.width;
	nph = (h + margins.height - 1) / margins.height;
}

bool PrintDialog::OnPrintPage(int pno)
{
	wxDC* dc = GetDC();
	dc->SetClippingRegion(margins);
	int xoff = (pno - 1) % npw;
	int yoff = (pno - 1) / npw;
	xoff *= margins.width;
	yoff *= margins.height;
	dc->DrawBitmap(*bmp, margins.x - xoff, margins.y - yoff);
	return true;
}

void PrintDialog::DoPrint(wxCommandEvent &)
{
	if (!printdata)
		printdata = new wxPrintData;

	if (!pagedata)
		pagedata = new wxPageSetupDialogData(*printdata);

	// wxGTK-2.8.12/gnome-2.18.8: can't set margins in page setup
	// wxMAC: need to pop up 2 dialogs to get margins
	// why even bother using standard dialogs, then?
	// well, maybe under msw, where generic dialog may not even exist...
#ifndef __WXMSW__
	wxGenericPageSetupDialog psdlg2(dlg, pagedata);
	psdlg2.ShowModal();
	*printdata = psdlg2.GetPageSetupDialogData().GetPrintData();
	*pagedata = psdlg2.GetPageSetupDialogData();
#else
	wxPageSetupDialog psdlg(dlg, pagedata);
	psdlg.ShowModal();
	*printdata = psdlg.GetPageSetupDialogData().GetPrintData();
	*pagedata = psdlg.GetPageSetupDialogData();
#ifdef __WXMAC__
	// FIXME: is this necessary?  useful? functional?
	wxMacPageMarginsDialog pmdlg(dlg, pagedata);
	pmdlg.ShowModal();
	*printdata = pmdlg.GetPageSetupDialogData().GetPrintData();
	*pagedata = pmdlg.GetPageSetupDialogData();
#endif
#endif
	wxPrintDialogData prdlg(*printdata);
	wxPrinter printer(&prdlg);

	if (printer.Print(dlg, this))
	{
		wxString msg = _("Printed");
		systemScreenMessage(msg);
		wxButton* cb = wxStaticCast(dlg->FindWindow(wxID_CANCEL), wxButton);

		if (cb)
		{
			cb->SetLabel(_("&Close"));
			cb->SetFocus();
		}

		*printdata = printer.GetPrintDialogData().GetPrintData();
	}
}

void systemGbPrint(u8* data, int len, int pages, int feed, int pal, int cont)
{
	ModalPause mp; // this might take a while, so signal a pause
	GameArea* panel = wxGetApp().frame->GetPanel();
	static u16* accum_prdata;
	static int accum_prdata_len = 0, accum_prdata_size = 0;
	static u16 prdata[162 * 145] = {0};
	static int picno = 0;
	int lines = len / 40;
	u16* out = prdata + 162; // 1-pix top border

	for (int y = 0; y < lines / 8; y++)
	{
		for (int x = 0; x < 160 / 8; x++)
		{
			for (int k = 0; k < 8; k++)
			{
				int a = *data++;
				int b = *data++;

				for (int j = 0, mask = 0x80; j < 8; j++, mask >>= 1)
				{
					int c = ((a & mask) | ((b & mask) << 1)) >> (7 - j);

					// the guide I read said 0 is on top, but if I do that,
					// output seems reversed.  So either 11 in pal means
					// white, or 00 in c means bits 0-1 in pal.
					// the guide I read also said 00 is white and 11 is black
					// I'm going with "00 in c means bits 0-1 in pal"
					if (pal != 0xe4) // 11 10 01 00
						c = (pal & (3 << c * 2)) >> c * 2;

					out[j + k * 162] = systemGbPalette[c];
				}
			}

			out += 8;
		}

		out += 2 + 7 * 162; // 2-pix rhs border
	}

	// assume no bottom margin means "more coming"
	// probably ought to make this time out somehow
	// or at the very least dump when the game state changes
	u16* to_print = prdata;

	if ((gopts.print_auto_page && !(feed & 15)) || accum_prdata_len)
	{
		if (!accum_prdata_len)
			accum_prdata_len = 162; // top border

		accum_prdata_len += lines * 162;

		if (accum_prdata_size < accum_prdata_len)
		{
			if (!accum_prdata_size)
				accum_prdata = (u16*)calloc(accum_prdata_len, 2);
			else
				accum_prdata = (u16*)realloc(accum_prdata, accum_prdata_len * 2);

			accum_prdata_size = accum_prdata_len;
		}

		memcpy(accum_prdata + accum_prdata_len - lines * 162, prdata + 162,
		       lines * 162 * 2);

		if (gopts.print_auto_page && !(feed & 15))
			return;

		to_print = accum_prdata;
		lines = accum_prdata_len / 162 - 1;
		accum_prdata_len = 0;
	}

	if (gopts.print_screen_cap)
	{
		wxFileName fn = wxFileName(wxGetApp().frame->GetGamePath(gopts.scrshot_dir), wxEmptyString);
		int num = 1;

		do
		{
			wxString bfn;
			bfn.Printf(wxT("%s-print%02d"), panel->game_name().c_str(),
			           num++);

			if (captureFormat == 0)
				bfn.append(wxT(".png"));
			else // if(gopts.cap_format == 1)
				bfn.append(wxT(".bmp"));

			fn.SetFullName(bfn);
		}
		while (fn.FileExists());

		fn.Mkdir(0777, wxPATH_MKDIR_FULL);
		int d = systemColorDepth;
		int rs = systemRedShift, bs = systemBlueShift, gs = systemGreenShift;
		systemColorDepth = 16;
		systemRedShift = 10;
		systemGreenShift = 5;
		systemBlueShift = 0;
		wxString of = fn.GetFullPath();
		bool ret = captureFormat == 0 ?
		           utilWritePNGFile(of.mb_fn_str(), 160, lines, (u8*)to_print) :
		           utilWriteBMPFile(of.mb_fn_str(), 160, lines, (u8*)to_print);

		if (ret)
		{
			wxString msg;
			msg.Printf(_("Wrote printer output to %s"), of.c_str());
			systemScreenMessage(msg);
		}

		systemColorDepth = d;
		systemRedShift = rs;
		systemGreenShift = gs;
		systemBlueShift = bs;
		return;
	}

	PrintDialog dlg(to_print, lines, !(feed & 15));
	int ret = dlg.ShowModal();

	if (ret == wxID_OK)
	{
		accum_prdata_len = (lines + 1) * 162;

		if (to_print != accum_prdata)
		{
			if (accum_prdata_size < accum_prdata_len)
			{
				if (!accum_prdata_size)
					accum_prdata = (u16*)calloc(accum_prdata_len, 2);
				else
					accum_prdata = (u16*)realloc(accum_prdata, accum_prdata_len * 2);

				accum_prdata_size = accum_prdata_len;
			}

			memcpy(accum_prdata, to_print, accum_prdata_len * 2);
		}
	}
}

void systemScreenMessage(const wxString &msg)
{
	if (wxGetApp().frame && wxGetApp().frame->IsShown())
	{
		wxPuts(msg);
		MainFrame* f = wxGetApp().frame;
		GameArea* panel = f->GetPanel();

		if (!panel->osdtext.empty())
			f->PopStatusText();

		f->PushStatusText(msg);
		panel->osdtext = msg;
		panel->osdtime = systemGetClock();
	}
}

void systemScreenMessage(const char* msg)
{
	systemScreenMessage(wxString(msg, wxConvLibc));
}

bool systemCanChangeSoundQuality()
{
#ifndef NO_FFMPEG

	if (emulating)
	{
		GameArea* panel = wxGetApp().frame->GetPanel();

		if (panel)
			return !panel->IsRecording();
	}

#endif
	return wxGetApp().IsMainLoopRunning();
}

bool systemPauseOnFrame()
{
	if (pause_next)
	{
		pause_next = false;
		MainFrame* fr = wxGetApp().frame;
		fr->GetPanel()->Pause();
		return true;
	}

	return false;
}

void systemGbBorderOn()
{
	GameArea* panel = wxGetApp().frame->GetPanel();

	if (panel)
		panel->AddBorder();
}

class SoundDriver;
SoundDriver* systemSoundInit()
{
	soundShutdown();

	switch (gopts.audio_api)
	{
	case AUD_SDL:
		return new SoundSDL();
#ifndef NO_OAL

	case AUD_OPENAL:
		return newOpenAL();
#endif
#ifdef __WXMSW__

	case AUD_DIRECTSOUND:
		return newDirectSound();
#ifndef NO_XAUDIO2

	case AUD_XAUDIO2:
		return newXAudio2_Output();
#endif
#endif

	default:
		gopts.audio_api = 0;
	}

	return 0;
}

void systemOnWriteDataToSoundBuffer(const u16* finalWave, int length)
{
#ifndef NO_FFMPEG
	GameArea* panel = wxGetApp().frame->GetPanel();

	if (panel)
		panel->AddFrame(finalWave, length);

#endif
}

void systemOnSoundShutdown()
{
}

extern int (*remoteSendFnc)(char*, int);
extern int (*remoteRecvFnc)(char*, int);
extern void (*remoteCleanUpFnc)();

#ifndef __WXMSW__
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/poll.h>

static wxString pty_slave;
static int pty_master = -1;

static int debugReadPty(char* buf, int len)
{
	while (1)
	{
		struct pollfd fd;
		fd.fd = pty_master;
		fd.events = POLLIN;

		if (poll(&fd, 1, 200) != 0)
			return read(pty_master, buf, len);
		else
			return -2; // try to let repaints & such run
	}
}

static int debugWritePty(/* const */char* buf, int len)
{
	return write(pty_master, buf, len);
}

static void debugClosePty()
{
	if (pty_master >= 0)
	{
		close(pty_master);
		pty_master = -1;
	}
}

bool debugOpenPty()
{
	if (pty_master >= 0) // should never happen
		close(pty_master);

	const char* slave_name;

	if ((pty_master = posix_openpt(O_RDWR | O_NOCTTY)) < 0 ||
	        grantpt(pty_master) < 0 || unlockpt(pty_master) < 0 ||
	        !(slave_name = ptsname(pty_master)))
	{
		wxLogError(_("Error opening pseudo tty: %s"), wxString(strerror(errno),
		           wxConvLibc).c_str());

		if (pty_master >= 0)
		{
			close(pty_master);
			pty_master = -1;
		}

		return false;
	}

	pty_slave = wxString(slave_name, wxConvLibc);
	remoteSendFnc = debugWritePty;
	remoteRecvFnc = debugReadPty;
	remoteCleanUpFnc = debugClosePty;
	return true;
}

const wxString &debugGetSlavePty()
{
	return pty_slave;
}

bool debugWaitPty()
{
	if (pty_master < 0)
		return false;

	struct pollfd fd;
	fd.fd = pty_master;
	fd.events = POLLIN;
	return poll(&fd, 1, 100) > 0;
}
#endif

#include <wx/socket.h>

static wxSocketServer* debug_server = NULL;
wxSocketBase* debug_remote = NULL;

static int debugReadSock(char* buf, int len)
{
	debug_remote->SetFlags(wxSOCKET_BLOCK);
	MainFrame* f = wxGetApp().frame;
	// WaitForRead does not obey wxSOCKET_BLOCK
	// It's hard to prevent menu command selection while GUI is active
	// so this will likely fail if the user isn't careful
	// the only fix in place is to prevent recursive idle loop
	f->StartModal();
	bool done = debug_remote->WaitForRead(0, 200);
	f->StopModal();

	if (!done)
		return -2;

	debug_remote->Read(buf, len);

	if (debug_remote->Error())
		return -1;

	return debug_remote->LastCount();
}

static int debugWriteSock(char* buf, int len)
{
	debug_remote->SetFlags(wxSOCKET_WAITALL | wxSOCKET_BLOCK);
	debug_remote->Write(buf, len);

	if (debug_remote->Error())
		return -1;

	return debug_remote->LastCount();
}

static void debugCloseSock()
{
	delete debug_remote;
	debug_remote = NULL;
	delete debug_server;
	debug_server = NULL;
}

bool debugStartListen(int port)
{
	delete debug_server; // should never be necessary
	delete debug_remote;
	debug_remote = NULL;
	wxIPV4address addr;
	addr.Service(port);
	addr.AnyAddress(); // probably ought to have a flag to select any/localhost
	debug_server = new wxSocketServer(addr, wxSOCKET_REUSEADDR);
	remoteSendFnc = debugWriteSock;
	remoteRecvFnc = debugReadSock;
	remoteCleanUpFnc = debugCloseSock;

	if (debug_server->IsOk())
		return true;

	wxLogError(_("Error setting up server socket (%d)"), debug_server->LastError());
	return false;
}

bool debugWaitSocket()
{
	if (!debug_server)
		return false;

	if (debug_remote)
		return true;

	debug_server->WaitForAccept(0, 100);
	debug_remote = debug_server->Accept(false);
	return debug_remote != NULL;
}

void log(const char* defaultMsg, ...)
{
	static FILE* out = NULL;
	va_list valist;
	char buf[2048];
	va_start(valist, defaultMsg);
	vsnprintf(buf, 2048, defaultMsg, valist);
	va_end(valist);
	wxGetApp().log.append(wxString(buf, wxConvLibc));

	if (wxGetApp().IsMainLoopRunning())
	{
		LogDialog* d = wxGetApp().frame->logdlg;

		if (d && d->IsShown())
		{
			d->Update();
		}

		systemScreenMessage(buf);
	}

	if (out == NULL)
	{
		// FIXME: this should be an option
		wxFileName trace_log(wxGetApp().GetConfigurationPath(), wxT("trace.log"));
		out = fopen(trace_log.GetFullPath().mb_str(), "w");

		if (!out)
			return;
	}

	va_start(valist, defaultMsg);
	vfprintf(out, defaultMsg, valist);
	va_end(valist);
}
